home *** CD-ROM | disk | FTP | other *** search
/ Magnum One / Magnum One (Mid-American Digital) (Disc Manufacturing).iso / d12 / stevie.arc / NORMAL.C < prev    next >
Text File  |  1990-01-10  |  25KB  |  1,328 lines

  1. /*
  2.  * STevie - ST editor for VI enthusiasts.    ...Tim Thompson...twitch!tjt...
  3.  *
  4.  * Extensive modifications by:  Tony Andrews       onecom!wldrdg!tony
  5.  * Turbo C 1.5 port by: Denny Muscatelli 061988
  6.  */
  7.  
  8. /*
  9.  * This file contains the main routine for processing characters in
  10.  * command mode as well as routines for handling the operators.
  11.  */
  12.  
  13. #include "stevie.h"
  14.  
  15. static    void    doshift(), dodelete(), doput(), dochange();
  16. static    void    tabinout(), startinsert();
  17. static    bool_t    dojoin(), doyank();
  18.  
  19. /*
  20.  * Macro evaluates true if char 'c' is a valid identifier character
  21.  */
  22. #define    IDCHAR(c)    (isalpha(c) || isdigit(c) || (c) == '_')
  23.  
  24. /*
  25.  * 'can_undo' is a relatively temporary hack so I can debug the 'undo'
  26.  * code for various operations independently. If 'can_undo' is set,
  27.  * then the most recent edit can be undone. Otherwise, attempting to
  28.  * undo an edit will result in an apologetic message. Can_undo is
  29.  * cleared in the macro 'CHANGED', so that every change, by default,
  30.  * cannot be undone. If the undo code for an edit works, 'can_undo'
  31.  * should be set, AFTER the CHANGED macro is invoked.
  32.  */
  33. bool_t    can_undo = FALSE;
  34.  
  35. /*
  36.  * Operators
  37.  */
  38. #define    NOP    0        /* no pending operation */
  39. #define    DELETE    1
  40. #define    YANK    2
  41. #define    CHANGE    3
  42. #define    LSHIFT    4
  43. #define    RSHIFT    5
  44.  
  45. #define    CLEAROP    (operator = NOP)    /* clear any pending operator */
  46.  
  47. static    int    operator = NOP;        /* current pending operator */
  48.  
  49. /*
  50.  * When a cursor motion command is made, it is marked as being a character
  51.  * or line oriented motion. Then, if an operator is in effect, the operation
  52.  * becomes character or line oriented accordingly.
  53.  *
  54.  * Character motions are marked as being inclusive or not. Most char.
  55.  * motions are inclusive, but some (e.g. 'w') are not.
  56.  *
  57.  * Generally speaking, every command in normal() should either clear any
  58.  * pending operator (with CLEAROP), or set the motion type variable.
  59.  */
  60.  
  61. /*
  62.  * Motion types
  63.  */
  64. #define    MBAD    (-1)        /* 'bad' motion type marks unusable yank buf */
  65. #define    MCHAR    0
  66. #define    MLINE    1
  67.  
  68. static    int    mtype;            /* type of the current cursor motion */
  69. static    bool_t    mincl;            /* true if char motion is inclusive */
  70.  
  71. static    LPTR    startop;        /* cursor pos. at start of operator */
  72.  
  73. /*
  74.  * Operators can have counts either before the operator, or between the
  75.  * operator and the following cursor motion as in:
  76.  *
  77.  *    d3w or 3dw
  78.  *
  79.  * If a count is given before the operator, it is saved in opnum. If
  80.  * normal() is called with a pending operator, the count in opnum (if
  81.  * present) overrides any count that came later.
  82.  */
  83. static    int    opnum = 0;
  84.  
  85.  
  86. #define    DEFAULT1(x)    (((x) == 0) ? 1 : (x))
  87.  
  88. /*
  89.  * normal
  90.  *
  91.  * Execute a command in normal mode.
  92.  */
  93.  
  94. void
  95. normal(c)
  96. int c;
  97. {
  98.     char *p, *q;
  99.     int n;
  100.     bool_t flag = FALSE;
  101.     int type = 0;        /* used in some operations to modify type */
  102.     int dir = FORWARD;    /* search direction */
  103.     int nchar = NUL;
  104.     bool_t finish_op;
  105.  
  106.     /*
  107.      * If there is an operator pending, then the command we take
  108.      * this time will terminate it. Finish_op tells us to finish
  109.      * the operation before returning this time (unless the operation
  110.      * was cancelled.
  111.      */
  112.     finish_op = (operator != NOP);
  113.  
  114.     /*
  115.      * If we're in the middle of an operator AND we had a count before
  116.      * the operator, then that count overrides the current value of
  117.      * Prenum. What this means effectively, is that commands like
  118.      * "3dw" get turned into "d3w" which makes things fall into place
  119.      * pretty neatly.
  120.      */
  121.     if (finish_op) {
  122.         if (opnum != 0)
  123.             Prenum = opnum;
  124.     } else
  125.         opnum = 0;
  126.  
  127.     switch(c & 0xff){
  128.  
  129.     case K_HELP:
  130.         CLEAROP;
  131.         if (help()) {
  132.             screenclear();
  133.             updatescreen();
  134.         }
  135.         break;
  136.  
  137.     case CTRL('L'):
  138.         CLEAROP;
  139.         screenclear();
  140.         updatescreen();
  141.         break;
  142.  
  143.     case CTRL('D'):
  144.         CLEAROP;
  145.         if (Prenum)
  146.             P(P_SS) = (Prenum > Rows-1) ? Rows-1 : Prenum;
  147.         scrollup(P(P_SS));
  148.         onedown(P(P_SS));
  149.         updatescreen();
  150.         break;
  151.  
  152.     case CTRL('U'):
  153.         CLEAROP;
  154.         if (Prenum)
  155.             P(P_SS) = (Prenum > Rows-1) ? Rows-1 : Prenum;
  156.         scrolldown(P(P_SS));
  157.         oneup(P(P_SS));
  158.         updatescreen();
  159.         break;
  160.  
  161.     /*
  162.      * ^F and ^B are neat hacks, but don't take counts. This is very
  163.      * code-efficient, and does the right thing. I'll fix it later
  164.      * to take a count. The old code took a count, but didn't do the
  165.      * right thing in other respects (e.g. leaving some context).
  166.      */
  167.     case CTRL('F'):
  168. #if 1
  169.         screenclear();
  170.         stuffin("Lz\nM");
  171. #else
  172.         /*
  173.          * Old code
  174.          */
  175.         CLEAROP;
  176.         n = DEFAULT1(Prenum);
  177.         if ( ! onedown(Rows * n) )
  178.             beep();
  179.         cursupdate();
  180. #endif
  181.         break;
  182.  
  183.     case CTRL('B'):
  184. #if 1
  185.         screenclear();
  186.         stuffin("Hz-M");
  187. #else
  188.         /*
  189.          * Old code
  190.          */
  191.         CLEAROP;
  192.         n = DEFAULT1(Prenum);
  193.         if ( ! oneup(Rows * n) )
  194.             beep();
  195.         cursupdate();
  196. #endif
  197.         break;
  198.  
  199.     case CTRL('E'):
  200.         CLEAROP;
  201.         scrollup(DEFAULT1(Prenum));
  202.         updatescreen();
  203.         break;
  204.  
  205.     case CTRL('Y'):
  206.         CLEAROP;
  207.         scrolldown(DEFAULT1(Prenum));
  208.         updatescreen();
  209.         break;
  210.  
  211.     case 'z':
  212.         CLEAROP;
  213.         switch (vgetc()) {
  214.         case NL:        /* put Curschar at top of screen */
  215.         case CR:
  216.             *Topchar = *Curschar;
  217.             Topchar->index = 0;
  218.             updatescreen();
  219.             break;
  220.  
  221.         case '.':        /* put Curschar in middle of screen */
  222.             n = Rows/2;
  223.             goto dozcmd;
  224.  
  225.         case '-':        /* put Curschar at bottom of screen */
  226.             n = Rows-1;
  227.             /* fall through */
  228.  
  229.         dozcmd:
  230.             {
  231.                 register LPTR    *lp = Curschar;
  232.                 register int    l = 0;
  233.  
  234.                 while ((l < n) && (lp != NULL)) {
  235.                     l += plines(lp);
  236.                     *Topchar = *lp;
  237.                     lp = prevline(lp);
  238.                 }
  239.             }
  240.             Topchar->index = 0;
  241.             updatescreen();
  242.             break;
  243.  
  244.         default:
  245.             beep();
  246.         }
  247.         break;
  248.  
  249.     case CTRL('G'):
  250.         CLEAROP;
  251.         fileinfo();
  252.         break;
  253.  
  254.     case 'G':
  255.         mtype = MLINE;
  256.         *Curschar = *gotoline(Prenum);
  257.         break;
  258.  
  259.     case 'H':
  260.         mtype = MLINE;
  261.         *Curschar = *Topchar;
  262.         for (n = Prenum; n && onedown(1) ;n--)
  263.             ;
  264.         beginline(TRUE);
  265.         break;
  266.  
  267.     case 'M':
  268.         mtype = MLINE;
  269.         *Curschar = *Topchar;
  270.         for (n = 0; n < Rows/2 && onedown(1) ;n++)
  271.             ;
  272.         beginline(TRUE);
  273.         break;
  274.  
  275.     case 'L':
  276.         mtype = MLINE;
  277.         *Curschar = *prevline(Botchar);
  278.         for (n = Prenum; n && oneup(1) ;n--)
  279.             ;
  280.         beginline(TRUE);
  281.         break;
  282.  
  283.     case 'l':
  284.     case K_RARROW:
  285.     case ' ':
  286.         mtype = MCHAR;
  287.         mincl = FALSE;
  288.         n = DEFAULT1(Prenum);
  289.         while (n--) {
  290.             if ( ! oneright() )
  291.                 beep();
  292.         }
  293.         set_want_col = TRUE;
  294.         break;
  295.  
  296.     case 'h':
  297.     case K_LARROW:
  298.     case CTRL('H'):
  299.         mtype = MCHAR;
  300.         mincl = FALSE;
  301.         n = DEFAULT1(Prenum);
  302.         while (n--) {
  303.             if ( ! oneleft() )
  304.                 beep();
  305.         }
  306.         set_want_col = TRUE;
  307.         break;
  308.  
  309.     case '-':
  310.         flag = TRUE;
  311.         /* fall through */
  312.  
  313.     case 'k':
  314.     case K_UARROW:
  315.     case CTRL('P'):
  316.         mtype = MLINE;
  317.         if ( ! oneup(DEFAULT1(Prenum)) )
  318.             beep();
  319.         if (flag)
  320.             beginline(TRUE);
  321.         break;
  322.  
  323.     case '+':
  324.     case CR:
  325.     case NL:
  326.         flag = TRUE;
  327.         /* fall through */
  328.  
  329.     case 'j':
  330.     case K_DARROW:
  331.     case CTRL('N'):
  332.         mtype = MLINE;
  333.         if ( ! onedown(DEFAULT1(Prenum)) )
  334.             beep();
  335.         if (flag)
  336.             beginline(TRUE);
  337.         break;
  338.  
  339.     /*
  340.      * This is a strange motion command that helps make operators
  341.      * more logical. It is actually implemented, but not documented
  342.      * in the real 'vi'. This motion command actually refers to "the
  343.      * current line". Commands like "dd" and "yy" are really an alternate
  344.      * form of "d_" and "y_". It does accept a count, so "d3_" works to
  345.      * delete 3 lines.
  346.      */
  347.     case '_':
  348.     lineop:
  349.         mtype = MLINE;
  350.         onedown(DEFAULT1(Prenum)-1);
  351.         break;
  352.  
  353.     case '|':
  354.         mtype = MCHAR;
  355.         mincl = TRUE;
  356.         beginline(FALSE);
  357.         if (Prenum > 0)
  358.             *Curschar = *coladvance(Curschar, Prenum-1);
  359.         Curswant = Prenum - 1;
  360.         break;
  361.         
  362.     case CTRL(']'):            /* :ta to current identifier */
  363.         CLEAROP;
  364.         {
  365.             char    c;
  366.             LPTR    save;
  367.  
  368.             save = *Curschar;
  369.             /*
  370.              * First back up to start of identifier. This
  371.              * doesn't match the real vi but I like it a
  372.              * little better and it shouldn't bother anyone.
  373.              */
  374.             c = gchar(Curschar);
  375.             while (IDCHAR(c)) {
  376.                 if (!oneleft())
  377.                     break;
  378.                 c = gchar(Curschar);
  379.             }
  380.             if (!IDCHAR(c))
  381.                 oneright();
  382.  
  383.             stuffin(":ta ");
  384.             /*
  385.              * Now grab the chars in the identifier
  386.              */
  387.             c = gchar(Curschar);
  388.             while (IDCHAR(c)) {
  389.                 stuffin(mkstr(c));
  390.                 if (!oneright())
  391.                     break;
  392.                 c = gchar(Curschar);
  393.             }
  394.             stuffin("\n");
  395.  
  396.             *Curschar = save;    /* restore, in case of error */
  397.         }
  398.         break;
  399.  
  400.     case '%':
  401.         mtype = MCHAR;
  402.         mincl = TRUE;
  403.         {
  404.             LPTR    *pos;
  405.  
  406.             if ((pos = showmatch()) == NULL)
  407.                 beep();
  408.             else {
  409.                 setpcmark();
  410.                 *Curschar = *pos;
  411.                 set_want_col = TRUE;
  412.             }
  413.         }
  414.         break;
  415.         
  416.     /*
  417.      * Word Motions
  418.      */
  419.  
  420.     case 'B':
  421.         type = 1;
  422.         /* fall through */
  423.  
  424.     case 'b':
  425.         mtype = MCHAR;
  426.         mincl = FALSE;
  427.         set_want_col = TRUE;
  428.         for (n = DEFAULT1(Prenum); n > 0 ;n--) {
  429.             LPTR    *pos;
  430.  
  431.             if ((pos = bck_word(Curschar, type)) == NULL) {
  432.                 beep();
  433.                 break;
  434.             } else
  435.                 *Curschar = *pos;
  436.         }
  437.         break;
  438.  
  439.     case 'W':
  440.         type = 1;
  441.         /* fall through */
  442.  
  443.     case 'w':
  444.         /*
  445.          * This is a little strange. To match what the real vi
  446.          * does, we effectively map 'cw' to 'ce', and 'cW' to 'cE'.
  447.          * This seems impolite at first, but it's really more
  448.          * what we mean when we say 'cw'.
  449.          */
  450.         if (operator == CHANGE)
  451.             goto doecmd;
  452.  
  453.         mtype = MCHAR;
  454.         mincl = FALSE;
  455.         set_want_col = TRUE;
  456.         for (n = DEFAULT1(Prenum); n > 0 ;n--) {
  457.             LPTR    *pos;
  458.  
  459.             if ((pos = fwd_word(Curschar, type)) == NULL) {
  460.                 beep();
  461.                 break;
  462.             } else
  463.                 *Curschar = *pos;
  464.         }
  465.         break;
  466.  
  467.     case 'E':
  468.         type = 1;
  469.         /* fall through */
  470.  
  471.     case 'e':
  472.     doecmd:
  473.         mtype = MCHAR;
  474.         mincl = TRUE;
  475.         set_want_col = TRUE;
  476.         for (n = DEFAULT1(Prenum); n > 0 ;n--) {
  477.             LPTR    *pos;
  478.  
  479.             if ((pos = end_word(Curschar, type)) == NULL) {
  480.                 beep();
  481.                 break;
  482.             } else
  483.                 *Curschar = *pos;
  484.         }
  485.         break;
  486.  
  487.     case '$':
  488.         mtype = MCHAR;
  489.         mincl = TRUE;
  490.         while ( oneright() )
  491.             ;
  492.         Curswant = 999;        /* so we stay at the end */
  493.         break;
  494.  
  495.     case '^':
  496.         flag = TRUE;
  497.         /* fall through */
  498.  
  499.     case '0':
  500.         mtype = MCHAR;
  501.         mincl = TRUE;
  502.         beginline(flag);
  503.         break;
  504.  
  505.     case 'x':
  506.         CLEAROP;
  507.         if (lineempty())    /* can't do it on a blank line */
  508.             beep();
  509.         if (Prenum)
  510.             stuffnum(Prenum);
  511.         stuffin("d.");
  512.         break;
  513.  
  514. #if 0
  515.         /* Can't do it if we're on a blank line. */
  516.         if (lineempty())
  517.             beep();
  518.         else {
  519.             addtobuff(Redobuff,'x',NULL);
  520.             /* To undo it, we insert the same character back. */
  521.             resetundo();
  522.             addtobuff(Undobuff, 'i', gchar(Curschar), ESC, NUL);
  523.             *Uncurschar = *Curschar;
  524.             delchar(TRUE);
  525.             updateline();
  526.         }
  527.         break;
  528. #endif
  529.  
  530.     case 'X':
  531.         CLEAROP;
  532.         if (!oneleft())
  533.             beep();
  534.         else {
  535.             addtobuff(Redobuff, 'X', NUL);
  536.             resetundo();
  537.             addtobuff(Undobuff, 'i', gchar(Curschar), ESC, NUL);
  538.             *Uncurschar = *Curschar;
  539.             delchar(TRUE);
  540.             updateline();
  541.         }
  542.         break;
  543.  
  544.     case 'A':
  545.         set_want_col = TRUE;
  546.         while (oneright())
  547.             ;
  548.         /* fall through */
  549.  
  550.     case 'a':
  551.         CLEAROP;
  552.         /* Works just like an 'i'nsert on the next character. */
  553.         if (!lineempty())
  554.             inc(Curschar);
  555.         resetundo();
  556.         startinsert(mkstr(c), FALSE);
  557.         break;
  558.  
  559.     case 'I':
  560.         beginline(TRUE);
  561.         /* fall through */
  562.  
  563.     case 'i':
  564.     case K_INSERT:
  565.         CLEAROP;
  566.         resetundo();
  567.         startinsert(mkstr(c), FALSE);
  568.         break;
  569.  
  570.     case 'o':
  571.         CLEAROP;
  572.         opencmd(FORWARD, TRUE);
  573.         resetundo();
  574.         addtobuff(Undobuff, 'J', NULL);
  575.         startinsert("o", TRUE);
  576.         break;
  577.  
  578.     case 'O':
  579.         CLEAROP;
  580.         opencmd(BACKWARD, TRUE);
  581.         resetundo();
  582.         startinsert("O", TRUE);
  583.         break;
  584.  
  585.     case 'd':
  586.         if (operator == DELETE)        /* handle 'dd' */
  587.             goto lineop;
  588.         if (Prenum != 0)
  589.             opnum = Prenum;
  590.         startop = *Curschar;
  591.         operator = DELETE;
  592.         break;
  593.  
  594.     /*
  595.      * Some convenient abbreviations...
  596.      */
  597.  
  598.     case 'D':
  599.         stuffin("d$");
  600.         break;
  601.  
  602.     case 'Y':
  603.         if (Prenum)
  604.             stuffnum(Prenum);
  605.         stuffin("yy");
  606.         break;
  607.  
  608.     case 'C':
  609.         stuffin("c$");
  610.         break;
  611.  
  612.     case 'c':
  613.         if (operator == CHANGE) {    /* handle 'cc' */
  614.             CLEAROP;
  615.             stuffin("0c$");
  616.             break;
  617.         }
  618.         if (Prenum != 0)
  619.             opnum = Prenum;
  620.         startop = *Curschar;
  621.         operator = CHANGE;
  622.         break;
  623.  
  624.     case 'y':
  625.         if (operator == YANK)        /* handle 'yy' */
  626.             goto lineop;
  627.         if (Prenum != 0)
  628.             opnum = Prenum;
  629.         startop = *Curschar;
  630.         operator = YANK;
  631.         break;
  632.  
  633.     case 'p':
  634.         doput(FORWARD);
  635.         break;
  636.  
  637.     case 'P':
  638.         doput(BACKWARD);
  639.         break;
  640.  
  641.     case '>':
  642.         if (operator == RSHIFT)        /* handle >> */
  643.             goto lineop;
  644.         if (Prenum != 0)
  645.             opnum = Prenum;
  646.         startop = *Curschar;
  647.         operator = RSHIFT;
  648.         break;
  649.  
  650.     case '<':
  651.         if (operator == LSHIFT)        /* handle << */
  652.             goto lineop;
  653.         if (Prenum != 0)
  654.             opnum = Prenum;
  655.         startop = *Curschar;    /* save current position */
  656.         operator = LSHIFT;
  657.         break;
  658.  
  659.     case 's':                /* substitute characters */
  660.         if (Prenum)
  661.             stuffnum(Prenum);
  662.         stuffin("c.");
  663.         break;
  664.  
  665.     case '?':
  666.     case '/':
  667.     case ':':
  668.         CLEAROP;
  669.         readcmdline(c, NULL);
  670.         break;
  671.  
  672.     case 'n':
  673.         mtype = MCHAR;
  674.         mincl = FALSE;
  675.         set_want_col = TRUE;
  676.         repsearch(0);
  677.         break;
  678.  
  679.     case 'N':
  680.         mtype = MCHAR;
  681.         mincl = FALSE;
  682.         set_want_col = TRUE;
  683.         repsearch(1);
  684.         break;
  685.  
  686.     /*
  687.      * Character searches
  688.      */
  689.     case 'T':
  690.         dir = BACKWARD;
  691.         /* fall through */
  692.  
  693.     case 't':
  694.         type = 1;
  695.         goto docsearch;
  696.  
  697.     case 'F':
  698.         dir = BACKWARD;
  699.         /* fall through */
  700.  
  701.     case 'f':
  702.     docsearch:
  703.         mtype = MCHAR;
  704.         mincl = TRUE;
  705.         set_want_col = TRUE;
  706.         if ((nchar = vgetc()) == ESC)    /* search char */
  707.             break;
  708.         if (!searchc(nchar, dir, type))
  709.             beep();
  710.         break;
  711.  
  712.     case ',':
  713.         flag = 1;
  714.         /* fall through */
  715.  
  716.     case ';':
  717.         mtype = MCHAR;
  718.         mincl = TRUE;
  719.         set_want_col = TRUE;
  720.         if (!crepsearch(flag))
  721.             beep();
  722.         break;
  723.  
  724.     /*
  725.      * Function searches
  726.      */
  727.  
  728.     case '[':
  729.         dir = BACKWARD;
  730.         /* fall through */
  731.  
  732.     case ']':
  733.         mtype = MLINE;
  734.         set_want_col = TRUE;
  735.         if (vgetc() != c)
  736.             beep();
  737.  
  738.         if (!findfunc(dir))
  739.             beep();
  740.         break;
  741.  
  742.     /*
  743.      * Marks
  744.      */
  745.  
  746.     case 'm':
  747.         CLEAROP;
  748.         if (!setmark(vgetc()))
  749.             beep();
  750.         break;
  751.  
  752.     case '\'':
  753.         flag = TRUE;
  754.         /* fall through */
  755.  
  756.     case '`':
  757.         {
  758.             LPTR    mtmp, *mark = getmark(vgetc());
  759.  
  760.             if (mark == NULL)
  761.                 beep();
  762.             else {
  763.                 mtmp = *mark;
  764.                 setpcmark();
  765.                 *Curschar = mtmp;
  766.                 if (flag)
  767.                     beginline(TRUE);
  768.             }
  769.             mtype = flag ? MLINE : MCHAR;
  770.             mincl = TRUE;        /* ignored if not MCHAR */
  771.             set_want_col = TRUE;
  772.         }
  773.         break;
  774.  
  775.     case 'r':
  776.         CLEAROP;
  777.         if (lineempty()) {    /* Nothing to replace */
  778.             beep();
  779.             break;
  780.         }
  781.         if ((nchar = vgetc()) == ESC)
  782.             break;
  783.         resetundo();
  784.  
  785.         addtobuff(Undobuff, 'r', gchar(Curschar), NULL);
  786.         *Uncurschar = *Curschar;
  787.  
  788.         /* Change current character. */
  789.         pchar(Curschar, nchar);
  790.  
  791.         /* Save stuff necessary to redo it */
  792.         addtobuff(Redobuff, 'r', nchar, NULL);
  793.  
  794.         CHANGED;
  795.         can_undo = TRUE;
  796.         updateline();
  797.         break;
  798.  
  799.     case '~':        /* swap case */
  800.         CLEAROP;
  801.         if (lineempty()) {
  802.             beep();
  803.             break;
  804.         }
  805.         c = gchar(Curschar);
  806.  
  807.         if (isalpha(c)) {
  808.             stuffin("r");        /* replace with other case */
  809.             if (islower(c))
  810.                 stuffin(mkstr(toupper(c)));
  811.             else
  812.                 stuffin(mkstr(tolower(c)));
  813.         }
  814.         stuffin("l");            /* move right when done */
  815.  
  816.         break;
  817.  
  818.     case 'J':
  819.         CLEAROP;
  820.  
  821.         if (!dojoin())
  822.             beep();
  823.  
  824.         resetundo();
  825.         *Uncurschar = *Curschar;
  826.         addtobuff(Undobuff, 'i', NL, ESC, NULL);
  827.         addtobuff(Redobuff,'J',NULL);
  828.         updatescreen();
  829.         break;
  830.  
  831.     case K_CGRAVE:            /* shorthand command */
  832.         CLEAROP;
  833.         stuffin(":e #\n");
  834.         break;
  835.  
  836.     case 'Z':            /* write, if changed, and exit */
  837.         if (vgetc() != 'Z') {
  838.             beep();
  839.             break;
  840.         }
  841.  
  842.         if (Changed) {
  843.             if (Filename != NULL) {
  844.                 if (!writeit(Filename, NULL, NULL))
  845.                     return;
  846.             } else {
  847.                 emsg("No output file");
  848.                 return;
  849.             }
  850.         }
  851.         getout();
  852.         break;
  853.  
  854.     case '.':
  855.         /*
  856.          * If a delete is in effect, we let '.' help out the same
  857.          * way that '_' helps for some line operations. It's like
  858.          * an 'l', but subtracts one from the count and is inclusive.
  859.          */
  860.         if (operator == DELETE || operator == CHANGE) {
  861.             if (Prenum != 0) {
  862.                 n = DEFAULT1(Prenum) - 1;
  863.                 while (n--)
  864.                     if (! oneright())
  865.                         break;
  866.             }
  867.             mtype = MCHAR;
  868.             mincl = TRUE;
  869.         } else {            /* a normal 'redo' */
  870.             CLEAROP;
  871.             stuffin(Redobuff);
  872.         }
  873.         break;
  874.  
  875.     case 'u':
  876.     case K_UNDO:
  877.         CLEAROP;
  878.         if (!can_undo) {
  879.             msg("Sorry, can't undo last edit");
  880.             break;
  881.         }
  882.  
  883.         if ( *Undobuff != NUL ) {
  884.             *Curschar = *Uncurschar;
  885.             stuffin(Undobuff);
  886.             *Undobuff = NUL;
  887.         }
  888.         if ( Undelchars > 0 ) {
  889.             *Curschar = *Uncurschar;
  890.             /* construct the next Undobuff and Redobuff, which */
  891.             /* will re-insert the characters we're deleting. */
  892.             p = Undobuff;
  893.             q = Redobuff;
  894.             *p++ = *q++ = 'i';
  895.             /*
  896.              * Fix this loop to effectively turn nulls into
  897.              * NL's in the Undo and Redo buffs and do the
  898.              * joins needed.
  899.              */
  900.             while ( Undelchars-- > 0 ) {
  901.                 char    c = gchar(Curschar);
  902.  
  903.                 if (c == NUL) {
  904.                     *p++ = *q++ = NL;
  905.                     dojoin();
  906.                 } else {
  907.                     *p++ = *q++ = c;
  908.                     delchar(FALSE);
  909.                 }
  910.             }
  911.             /* Finish constructing Uncursbuff, and Uncurschar */
  912.             /* is left unchanged. */
  913.             *p++ = *q++ = ESC;
  914.             *p = *q = NUL;
  915.             /* Undelchars has been reset to 0 */
  916.             updatescreen();
  917.         }
  918.         can_undo = FALSE;
  919.         break;
  920.  
  921.     default:
  922.         CLEAROP;
  923.         beep();
  924.         break;
  925.     }
  926.  
  927.     /*
  928.      * If an operation is pending, handle it...
  929.      */
  930.     if (finish_op) {        /* we just finished an operator */
  931.         if (operator == NOP)    /* ... but it was cancelled */
  932.             return;
  933.  
  934.         switch (operator) {
  935.  
  936.         case LSHIFT:
  937.         case RSHIFT:
  938.             doshift(operator, c, nchar, Prenum);
  939.             break;
  940.  
  941.         case DELETE:
  942.             dodelete(c, nchar, Prenum);
  943.             break;
  944.  
  945.         case YANK:
  946.             doyank();    /* no redo on yank... */
  947.             break;
  948.  
  949.         case CHANGE:
  950.             dochange(c, nchar, Prenum);
  951.             break;
  952.  
  953.         default:
  954.             beep();
  955.         }
  956.         operator = NOP;
  957.     }
  958. }
  959.  
  960. /*
  961.  * doshift - handle a shift operation
  962.  */
  963. static void
  964. doshift(op, c1, c2, num)
  965. int    op;
  966. char    c1, c2;
  967. int    num;
  968. {
  969.     LPTR    top, bot;
  970.     int    nlines;
  971.     char    opchar;
  972.  
  973.     top = startop;
  974.     bot = *Curschar;
  975.  
  976.     if (lt(&bot, &top))
  977.         pswap(&top, &bot);
  978.  
  979.     nlines = cntllines(&top, &bot);
  980.     *Curschar = top;
  981.     tabinout((op == LSHIFT), nlines);
  982.  
  983.     /* construct Redo buff */
  984.     opchar = (op == LSHIFT) ? '<' : '>';
  985.     if (num != 0)
  986.         sprintf(Redobuff, "%c%d%c%c", opchar, num, c1, c2);
  987.     else
  988.         sprintf(Redobuff, "%c%c%c", opchar, c1, c2);
  989.  
  990.     /*
  991.      * The cursor position afterward is the prior of the two positions.
  992.      */
  993.     *Curschar = top;
  994.  
  995.     /*
  996.      * If we were on the last char of a line that got shifted left,
  997.      * then move left one so we aren't beyond the end of the line
  998.      */
  999.     if (gchar(Curschar) == NUL && Curschar->index > 0)
  1000.         Curschar->index--;
  1001.  
  1002.     updatescreen();
  1003.  
  1004.     if (nlines > P(P_RP))
  1005.         smsg("%d lines %ced", nlines, opchar);
  1006. }
  1007.  
  1008. /*
  1009.  * dodelete - handle a delete operation
  1010.  */
  1011. static void
  1012. dodelete(c1, c2, num)
  1013. char    c1, c2;
  1014. int    num;
  1015. {
  1016.     LPTR    top, bot;
  1017.     int    nlines;
  1018.     int    n;
  1019.  
  1020.     /*
  1021.      * Do a yank of whatever we're about to delete. If there's too much
  1022.      * stuff to fit in the yank buffer, then get a confirmation before
  1023.      * doing the delete. This is crude, but simple. And it avoids doing
  1024.      * a delete of something we can't put back if we want.
  1025.      */
  1026.     if (!doyank()) {
  1027.         msg("yank buffer exceeded: press <y> to confirm");
  1028.         if (vgetc() != 'y') {
  1029.             msg("delete aborted");
  1030.             *Curschar = startop;
  1031.             return;
  1032.         }
  1033.     }
  1034.  
  1035.     top = startop;
  1036.     bot = *Curschar;
  1037.  
  1038.     if (lt(&bot, &top))
  1039.         pswap(&top, &bot);
  1040.  
  1041.     nlines = cntllines(&top, &bot);
  1042.     *Curschar = top;
  1043.     cursupdate();
  1044.  
  1045.     if (mtype == MLINE) {
  1046.         delline(nlines);
  1047.     } else {
  1048.         if (!mincl && bot.index != 0)
  1049.             dec(&bot);
  1050.  
  1051.         if (top.linep == bot.linep) {        /* del. within line */
  1052.             n = bot.index - top.index + 1;
  1053.             while (n--)
  1054.                 if (!delchar(TRUE))
  1055.                     break;
  1056.         } else {                /* del. between lines */
  1057.             n = Curschar->index;
  1058.             while (Curschar->index >= n)
  1059.                 if (!delchar(TRUE))
  1060.                     break;
  1061.  
  1062.             top = *Curschar;
  1063.             *Curschar = *nextline(Curschar);
  1064.             delline(nlines-2);
  1065.             Curschar->index = 0;
  1066.             n = bot.index + 1;
  1067.             while (n--)
  1068.                 if (!delchar(TRUE))
  1069.                     break;
  1070.             *Curschar = top;
  1071.             dojoin();
  1072.         }
  1073.     }
  1074.  
  1075.     /* construct Redo buff */
  1076.     if (num != 0)
  1077.         sprintf(Redobuff, "d%d%c%c", num, c1, c2);
  1078.     else
  1079.         sprintf(Redobuff, "d%c%c", c1, c2);
  1080.  
  1081.     if (mtype == MCHAR && nlines == 1)
  1082.         updateline();
  1083.     else
  1084.         updatescreen();
  1085.  
  1086.     if (nlines > P(P_RP))
  1087.         smsg("%d fewer lines", nlines);
  1088. }
  1089.  
  1090. /*
  1091.  * dochange - handle a change operation
  1092.  */
  1093. static void
  1094. dochange(c1, c2, num)
  1095. char    c1, c2;
  1096. int    num;
  1097. {
  1098.     char    sbuf[16];
  1099.     bool_t    doappend;    /* true if we should do append, not insert */
  1100.  
  1101.     doappend = endofline( (lt(Curschar, &startop)) ? &startop: Curschar);
  1102.  
  1103.     if (mtype == MLINE) {
  1104.         msg("multi-line changes not yet supported");
  1105.         return;
  1106.     }
  1107.  
  1108.     dodelete(c1, c2, num);
  1109.  
  1110.     if (num)
  1111.         sprintf(sbuf, "c%d%c%c", num, c1, c2);
  1112.     else
  1113.         sprintf(sbuf, "c%c%c", c1, c2);
  1114.  
  1115.     if (doappend && !lineempty())
  1116.         inc(Curschar);
  1117.  
  1118.     startinsert(sbuf);
  1119. }
  1120.  
  1121. #define    YBSIZE    1024
  1122.  
  1123. static    char    ybuf[YBSIZE];
  1124. static    int    ybtype = MBAD;
  1125.  
  1126. static bool_t
  1127. doyank()
  1128. {
  1129.     LPTR    top, bot;
  1130.     char    *yptr = ybuf;
  1131.     char    *ybend = &ybuf[YBSIZE-1];
  1132.     int    nlines;
  1133.  
  1134.     top = startop;
  1135.     bot = *Curschar;
  1136.  
  1137.     if (lt(&bot, &top))
  1138.         pswap(&top, &bot);
  1139.  
  1140.     nlines = cntllines(&top, &bot);
  1141.  
  1142.     ybtype = mtype;            /* set the yank buffer type */
  1143.  
  1144.     if (mtype == MLINE) {
  1145.         top.index = 0;
  1146.         bot.index = strlen(bot.linep->s);
  1147.         /*
  1148.          * The following statement checks for the special case of
  1149.          * yanking a blank line at the beginning of the file. If
  1150.          * not handled right, we yank an extra char (a newline).
  1151.          */
  1152.         if (dec(&bot) == -1) {
  1153.             ybuf[0] = NUL;
  1154.             if (operator == YANK)
  1155.                 *Curschar = startop;
  1156.             return TRUE;
  1157.         }
  1158.     } else {
  1159.         if (!mincl) {
  1160.             if (bot.index)
  1161.                 bot.index--;
  1162.         }
  1163.     }
  1164.  
  1165.     for (; ltoreq(&top, &bot) ;inc(&top)) {
  1166.         *yptr = (gchar(&top) != NUL) ? gchar(&top) : NL;
  1167.         if (++yptr >= ybend) {
  1168.             msg("yank too big for buffer");
  1169.             ybtype = MBAD;
  1170.             return FALSE;
  1171.         }
  1172.     }
  1173.  
  1174.     *yptr = NUL;
  1175.  
  1176.     if (operator == YANK) {    /* restore Curschar if really doing yank */
  1177.         *Curschar = startop;
  1178.  
  1179.         if (nlines > P(P_RP))
  1180.             smsg("%d lines yanked", nlines);
  1181.     }
  1182.  
  1183.     return TRUE;
  1184. }
  1185.  
  1186. static void
  1187. doput(dir)
  1188. int    dir;
  1189. {
  1190.     if (ybtype == MBAD) {
  1191.         beep();
  1192.         return;
  1193.     }
  1194.     
  1195.     if (dir == FORWARD)
  1196.         stuffin( (ybtype == MCHAR) ? "a" : "o" );
  1197.     else
  1198.         stuffin( (ybtype == MCHAR) ? "i" : "O" );
  1199.  
  1200.     stuffin(ybuf);
  1201.     stuffin(mkstr(ESC));
  1202. }
  1203.  
  1204. /*
  1205.  * tabinout(inout,num)
  1206.  *
  1207.  * If inout==0, add a tab to the begining of the next num lines.
  1208.  * If inout==1, delete a tab from the beginning of the next num lines.
  1209.  */
  1210. static void
  1211. tabinout(inout, num)
  1212. int    inout;
  1213. int    num;
  1214. {
  1215.     int    ntodo = num;
  1216.     LPTR    *p;
  1217.  
  1218.     /* construct undo stuff */
  1219.     resetundo();
  1220.     *Uncurschar = *Curschar;
  1221.     sprintf(Undobuff, "%d%s", num, (inout == 0) ? "<<" : ">>");
  1222.  
  1223.     beginline(FALSE);
  1224.     while ( ntodo-- > 0 ) {
  1225.         beginline(FALSE);
  1226.         if ( inout == 0 )
  1227.             inschar(TAB);
  1228.         else {
  1229.             if ( gchar(Curschar) == TAB )
  1230.                 delchar(TRUE);
  1231.         }
  1232.         if ( ntodo > 0 ) {
  1233.             if ( (p=nextline(Curschar)) != NULL )
  1234.                 *Curschar = *p;
  1235.             else
  1236.                 break;
  1237.         }
  1238.     }
  1239.     can_undo = TRUE;
  1240. }
  1241.  
  1242. static void
  1243. startinsert(initstr, startln)
  1244. char *initstr;
  1245. int    startln;    /* if set, insert point really at start of line */
  1246. {
  1247.     char *p, c;
  1248.  
  1249.     *Insstart = *Curschar;
  1250.     if (startln)
  1251.         Insstart->index = 0;
  1252.     Ninsert = 0;
  1253.     Insptr = Insbuff;
  1254.     for (p=initstr; (c=(*p++))!='\0'; )
  1255.         *Insptr++ = c;
  1256.     State = INSERT;
  1257.     if (P(P_MO))
  1258.         msg("Insert Mode");
  1259. }
  1260.  
  1261. void
  1262. resetundo()
  1263. {
  1264.     Undelchars = 0;
  1265.     *Undobuff = '\0';
  1266.     Uncurschar->linep = NULL;
  1267. }
  1268.  
  1269. static bool_t
  1270. dojoin()
  1271. {
  1272.     int    scol;        /* save cursor column */
  1273.     int    size;        /* size of the joined line */
  1274.  
  1275.     if (nextline(Curschar) == NULL)        /* on last line */
  1276.         return FALSE;
  1277.  
  1278.     if (!canincrease(size = strlen(Curschar->linep->next->s)))
  1279.         return FALSE;
  1280.  
  1281.     while (oneright())            /* to end of line */
  1282.         ;
  1283.  
  1284.     strcat(Curschar->linep->s, Curschar->linep->next->s);
  1285.  
  1286.     /*
  1287.      * Delete the following line. To do this we move the cursor
  1288.      * there briefly, and then move it back. Don't back up if the
  1289.      * delete made us the last line.
  1290.      */
  1291.     Curschar->linep = Curschar->linep->next;
  1292.     scol = Curschar->index;
  1293.  
  1294.     if (nextline(Curschar) != NULL) {
  1295.         delline(1);
  1296.         Curschar->linep = Curschar->linep->prev;
  1297.     } else
  1298.         delline(1);
  1299.  
  1300.     Curschar->index = scol;
  1301.  
  1302.     oneright();        /* go to first char. of joined line */
  1303.  
  1304.     if (size != 0) {
  1305.         /*
  1306.          * Delete leading white space on the joined line
  1307.          * and insert a single space.
  1308.          */
  1309.         while (gchar(Curschar) == ' ' || gchar(Curschar) == TAB)
  1310.             delchar(TRUE);
  1311.         inschar(' ');
  1312.     }
  1313.  
  1314.     return TRUE;
  1315. }
  1316.  
  1317. char *
  1318. mkstr(c)
  1319. char    c;
  1320. {
  1321.     static    char    s[2];
  1322.  
  1323.     s[0] = c;
  1324.     s[1] = NUL;
  1325.  
  1326.     return s;
  1327. }
  1328.